#include "Renderer.h"


// used to make debug easier when dealing with DirectX
#ifdef _DEBUG
	#ifndef HR

	#define HR(x)					\
	{								\
		HRESULT hr = x;				\
		if (FAILED(hr))				\
		{							\
			DXTRACE_ERR(#x, hr);	\
			DebugBreak();			\
		}							\
	}
	#endif
#else
	#ifndef HR
	#define HR(x) x;
	#endif
#endif

// initializes most member variables to default values
Renderer::Renderer()
	: m_pD3DDevice(NULL)
	, m_pD3DFont(NULL)
	, m_pD3DSprite(NULL)
	, m_lpTexture(NULL)
	, m_hWnd(NULL)
	, m_bWindowed(true)
	, m_fScreenHeight(0)
	, m_fScreenWidth(0)
	, m_fps(0)
	, m_fpsFontSize(30)
{
	m_oldTime = time(NULL);	// grabs initial time in seconds
}

void Renderer::Init(HWND hWnd)
{
	// seed random generator
	srand((int)time(0));

	SetupDevice(hWnd);
	SetupFont();
	SetupSprites();

}

void Renderer::SetupDevice(HWND hWnd)
{
	IDirect3D9* pD3DObject = Direct3DCreate9(D3D_SDK_VERSION);	// create the d3dobject

	m_hWnd = hWnd;										// make a copy of hWnd that Renderer can use

	RECT rClientRect;									// grab the screen dimensions
	if(!::GetClientRect(m_hWnd, &rClientRect))
	{
		throw "GetClientRect() failed";
	}
	m_fScreenWidth = (float)(rClientRect.right - rClientRect.left);
	m_fScreenHeight = (float)(rClientRect.bottom - rClientRect.top);

	D3DCAPS9 D3DCaps;
	HR(pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));


	// determine what type of vertex processing to use
	DWORD dwBehaviorFlags = 0;

	if(D3DCaps.VertexProcessingCaps != 0)
	{
		dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	}
	else
	{
		dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}	

	//set up present parameters
	m_D3Dpp.hDeviceWindow			= m_hWnd;
	m_D3Dpp.Windowed				= m_bWindowed;
	m_D3Dpp.AutoDepthStencilFormat	= D3DFMT_D24S8;
	m_D3Dpp.EnableAutoDepthStencil	= TRUE;
	m_D3Dpp.BackBufferCount			= 1;
	m_D3Dpp.BackBufferFormat		= D3DFMT_X8R8G8B8;
	m_D3Dpp.BackBufferWidth			= (int)m_fScreenWidth;
	m_D3Dpp.BackBufferHeight		= (int)m_fScreenHeight;
	m_D3Dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	m_D3Dpp.PresentationInterval	= D3DPRESENT_INTERVAL_IMMEDIATE;
	m_D3Dpp.Flags					= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	m_D3Dpp.MultiSampleQuality		= 0;
	m_D3Dpp.MultiSampleType			= D3DMULTISAMPLE_NONE;

	// setup the d3ddevice using the d3dobject and present parameters
	pD3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, dwBehaviorFlags, &m_D3Dpp, &m_pD3DDevice);

	pD3DObject->Release();
}

void Renderer::SetupFont()
{
	// set up font used to display fps counter
	D3DXCreateFont(m_pD3DDevice, m_fpsFontSize, 0, FW_BOLD, 0, false,
					DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
					DEFAULT_PITCH | FF_DONTCARE, TEXT("Times New Roman"), &m_pD3DFont);
}

void Renderer::SetupSprites()
{
	// set up sprite handler
	D3DXCreateSprite(m_pD3DDevice, &m_pD3DSprite);

	// create sprite texture and image info
	D3DXCreateTextureFromFileEx(m_pD3DDevice, "test.tga", 0, 0, 0, 0,
										D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT,
										D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255),
										&m_imageInfo, 0, &m_lpTexture);

	// set up array of objects
	for(int i = 0; i < 6; i++)
	{

		// randomly sets the sprites' rotation
		sprites[i].fRotation = (float)(rand() % 360);
		sprites[i].fRotation = D3DXToRadian(sprites[i].fRotation);
		sprites[i].fAngle = sprites[i].fRotation;

		// randomly sets the sprites' scale
		sprites[i].fScale = (float)(50 + rand() % 300) / 100;

		// randomly sets the sprites' color
		sprites[i].color = D3DCOLOR_XRGB(rand() % 255, rand() % 255, rand() % 255);

		// randomly set the sprites' position, making sure that not even the edges of the sprite will go out of bounds
		sprites[i].vPos = D3DXVECTOR3(
								(float)(rand() % (int)(m_fScreenWidth - m_imageInfo.Width * 0.5f * sprites[i].fScale)), 
								(float)(rand() % (int)(m_fScreenHeight - m_imageInfo.Height * 0.5f * sprites[i].fScale)), 0);

		// sets the final sprite in the array to constantly spin
		if(i == 5)
			sprites[i].isSpinning = true;
			
		sprites[i].vSpriteCenter.x = m_imageInfo.Width * 0.5f;
		sprites[i].vSpriteCenter.y = m_imageInfo.Height * 0.5f;
		sprites[i].vSpriteCenter.z = 0;
	}

}

void Renderer::Update()
{
	// update moving sprites
	for(int i = 0; i < 6; i++)
	{
		if(sprites[i].isSpinning)
		{
			sprites[i].fAngle += (float)((5) * .01);

			if(sprites[i].fAngle > 2 * PI)
				sprites[i].fAngle = 0;
		}
	}

	//update frames per second counter
	m_newTime = time(NULL);			// grab the new time

	if(m_newTime > m_oldTime)
	{
		m_fps = (float)m_frameCount;
		m_oldTime = m_newTime;
		m_frameCount = 0;
	}
}

void Renderer::RenderOneFrame()
{
	if(NULL == m_pD3DDevice) return;

	//check for lost device
	HRESULT hDevState = m_pD3DDevice->TestCooperativeLevel();
	switch(hDevState)
	{
	case D3DERR_DEVICELOST:
		// if device is lost, stop and check it next frame
		::OutputDebugString("Device Lost");
		::Sleep(1000);
		return;
	case D3DERR_DEVICENOTRESET:
		// device still not found after a while, reset the D3D device
		if(FAILED(m_pD3DDevice->Reset(&m_D3Dpp)))
		{
			::OutputDebugString("Reset failed");
			return;
		}
		break;
	}

	// there should be no more device related errors at this point


	// clear back buffer and z-buffer
	HR(m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0));

	// begin preparing the frame
	HRESULT hr = m_pD3DDevice->BeginScene();
	if(FAILED(hr))
	{
		//if preparation failed, stop rendering
		return;
	}


	D3DXMATRIX matRotation;
	D3DXMATRIX matScale;
	D3DXMATRIX matTrans;

	// prepare to draw sprites to the screen
	m_pD3DSprite->Begin(0);

	for(int i = 0; i < 6; i++)
	{
		//D3DXMatrixIdentity(&sprites[i].mWorld);

		//prepare the the translation, rotation, and scaling matrices
		D3DXMatrixTranslation(&matTrans,
								sprites[i].vPos.x, sprites[i].vPos.y, 0);
		D3DXMatrixRotationZ(&matRotation, sprites[i].fAngle);
		D3DXMatrixScaling(&matScale, sprites[i].fScale, sprites[i].fScale, 0);


		//apply the transformation matrices in the right order: Scale, Rotate, Translate
		D3DXMatrixMultiply(&sprites[i].mWorld, &matRotation, &matTrans);			//world = scale * rotation
		D3DXMatrixMultiply(&sprites[i].mWorld, &matScale, &sprites[i].mWorld);		//world *= position

		m_pD3DSprite->SetTransform(&sprites[i].mWorld);

		m_pD3DSprite->Draw(m_lpTexture, 0, &sprites[i].vSpriteCenter, 
								0, sprites[i].color);
	}


	m_pD3DSprite->End();
	// stop drawing sprites


	// setup rect used to display fps counter
	RECT fpsRect;

	//store the fps display in the stream
	m_oss << "FPS: " << m_fps;

	SetRect(&fpsRect, 0, 0, m_fpsFontSize * m_oss.str().size(), m_fpsFontSize);	
	m_pD3DFont->DrawTextA(NULL, m_oss.str().c_str(), m_oss.str().size(), &fpsRect, DT_LEFT | DT_NOCLIP, 
							D3DCOLOR_ARGB(255, 255, 255, 255));

	//clear the stream
	m_oss.clear();
	m_oss.str("");

	hr = m_pD3DDevice->EndScene();	// stop preparing the frame

	if(FAILED(hr)) 
	{
		// stop rendering if EndScene failed
		return;
	}

	//display back buffer onto the screen
	m_pD3DDevice->Present(NULL, NULL, NULL, NULL);

	// update the amount of frames that have gone been rendered
	m_frameCount++;
}

// release memory
void Renderer::Cleanup()
{
	//m_pD3DObject->Release();
	m_pD3DDevice->Release();
	m_pD3DFont->Release();
	m_pD3DSprite->Release();
	m_lpTexture->Release();
}
